package com.rabtman.common.utils;

import static android.Manifest.permission.ACCESS_NETWORK_STATE;
import static android.Manifest.permission.ACCESS_WIFI_STATE;
import static android.Manifest.permission.INTERNET;
import static android.Manifest.permission.MODIFY_PHONE_STATE;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.support.annotation.RequiresPermission;
import android.telephony.TelephonyManager;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;

/**
 * <pre>
 *     author: Blankj
 *     blog  : http://blankj.com
 *     time  : 2016/08/02
 *     desc  : utils about network
 * </pre>
 */
public final class NetworkUtils {

  private static final int NETWORK_TYPE_GSM = 16;
  private static final int NETWORK_TYPE_TD_SCDMA = 17;
  private static final int NETWORK_TYPE_IWLAN = 18;

  private NetworkUtils() {
    throw new UnsupportedOperationException("u can't instantiate me...");
  }

  /**
   * Open the settings of wireless.
   */
  public static void openWirelessSettings() {
    Utils.getContext().startActivity(
        new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS)
            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    );
  }

  /**
   * Return whether network is connected.
   * <p>Must hold
   * {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
   *
   * @return {@code true}: connected<br>{@code false}: disconnected
   */
  @RequiresPermission(ACCESS_NETWORK_STATE)
  public static boolean isConnected() {
    NetworkInfo info = getActiveNetworkInfo();
    return info != null && info.isConnected();
  }

  /**
   * Return whether mobile data is enabled.
   *
   * @return {@code true}: enabled<br>{@code false}: disabled
   */
  public static boolean getMobileDataEnabled() {
    try {
      TelephonyManager tm =
          (TelephonyManager) Utils.getContext().getSystemService(Context.TELEPHONY_SERVICE);
      if (tm == null) {
        return false;
      }
      @SuppressLint("PrivateApi")
      Method getMobileDataEnabledMethod = tm.getClass().getDeclaredMethod("getDataEnabled");
      if (null != getMobileDataEnabledMethod) {
        return (boolean) getMobileDataEnabledMethod.invoke(tm);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
    return false;
  }

  /**
   * Set mobile data enabled.
   * <p>Must hold {@code android:sharedUserId="android.uid.system"},
   * {@code <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />}</p>
   *
   * @param enabled True to enabled, false otherwise.
   */
  @RequiresPermission(MODIFY_PHONE_STATE)
  public static void setMobileDataEnabled(final boolean enabled) {
    try {
      TelephonyManager tm =
          (TelephonyManager) Utils.getContext().getSystemService(Context.TELEPHONY_SERVICE);
      if (tm == null) {
        return;
      }
      Method setMobileDataEnabledMethod =
          tm.getClass().getDeclaredMethod("setDataEnabled", boolean.class);
      if (null != setMobileDataEnabledMethod) {
        setMobileDataEnabledMethod.invoke(tm, enabled);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * Return whether using mobile data.
   * <p>Must hold
   * {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
   *
   * @return {@code true}: yes<br>{@code false}: no
   */
  @RequiresPermission(ACCESS_NETWORK_STATE)
  public static boolean isMobileData() {
    NetworkInfo info = getActiveNetworkInfo();
    return null != info
        && info.isAvailable()
        && info.getType() == ConnectivityManager.TYPE_MOBILE;
  }

  /**
   * Return whether using 4G.
   * <p>Must hold
   * {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
   *
   * @return {@code true}: yes<br>{@code false}: no
   */
  @RequiresPermission(ACCESS_NETWORK_STATE)
  public static boolean is4G() {
    NetworkInfo info = getActiveNetworkInfo();
    return info != null
        && info.isAvailable()
        && info.getSubtype() == TelephonyManager.NETWORK_TYPE_LTE;
  }

  /**
   * Return whether wifi is enabled.
   * <p>Must hold
   * {@code <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />}</p>
   *
   * @return {@code true}: enabled<br>{@code false}: disabled
   */
  @RequiresPermission(ACCESS_WIFI_STATE)
  public static boolean getWifiEnabled() {
    @SuppressLint("WifiManagerLeak")
    WifiManager manager = (WifiManager) Utils.getContext().getSystemService(Context.WIFI_SERVICE);
    return manager != null && manager.isWifiEnabled();
  }

  /**
   * Set wifi enabled.
   * <p>Must hold
   * {@code <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />}</p>
   *
   * @param enabled True to enabled, false otherwise.
   */
  @SuppressLint("MissingPermission")
  public static void setWifiEnabled(final boolean enabled) {
    @SuppressLint("WifiManagerLeak")
    WifiManager manager = (WifiManager) Utils.getContext().getSystemService(Context.WIFI_SERVICE);
    if (manager == null) {
      return;
    }
    if (enabled) {
      if (!manager.isWifiEnabled()) {
        manager.setWifiEnabled(true);
      }
    } else {
      if (manager.isWifiEnabled()) {
        manager.setWifiEnabled(false);
      }
    }
  }

  /**
   * Return whether wifi is connected.
   * <p>Must hold
   * {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
   *
   * @return {@code true}: connected<br>{@code false}: disconnected
   */
  @SuppressLint("MissingPermission")
  public static boolean isWifiConnected() {
    ConnectivityManager cm =
        (ConnectivityManager) Utils.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
    return cm != null
        && cm.getActiveNetworkInfo() != null
        && cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI;
  }

  /**
   * Return the name of network operate.
   *
   * @return the name of network operate
   */
  public static String getNetworkOperatorName() {
    TelephonyManager tm =
        (TelephonyManager) Utils.getContext().getSystemService(Context.TELEPHONY_SERVICE);
    return tm != null ? tm.getNetworkOperatorName() : null;
  }

  /**
   * Return type of network.
   * <p>Must hold
   * {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
   *
   * @return type of network <ul> <li>{@link NetworkUtils.NetworkType#NETWORK_WIFI   } </li>
   * <li>{@link NetworkUtils.NetworkType#NETWORK_4G     } </li> <li>{@link
   * NetworkUtils.NetworkType#NETWORK_3G     } </li> <li>{@link NetworkUtils.NetworkType#NETWORK_2G
   * } </li> <li>{@link NetworkUtils.NetworkType#NETWORK_UNKNOWN} </li> <li>{@link
   * NetworkUtils.NetworkType#NETWORK_NO     } </li> </ul>
   */
  @RequiresPermission(ACCESS_NETWORK_STATE)
  public static NetworkType getNetworkType() {
    NetworkType netType = NetworkType.NETWORK_NO;
    NetworkInfo info = getActiveNetworkInfo();
    if (info != null && info.isAvailable()) {

      if (info.getType() == ConnectivityManager.TYPE_WIFI) {
        netType = NetworkType.NETWORK_WIFI;
      } else if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
        switch (info.getSubtype()) {

          case NETWORK_TYPE_GSM:
          case TelephonyManager.NETWORK_TYPE_GPRS:
          case TelephonyManager.NETWORK_TYPE_CDMA:
          case TelephonyManager.NETWORK_TYPE_EDGE:
          case TelephonyManager.NETWORK_TYPE_1xRTT:
          case TelephonyManager.NETWORK_TYPE_IDEN:
            netType = NetworkType.NETWORK_2G;
            break;

          case NETWORK_TYPE_TD_SCDMA:
          case TelephonyManager.NETWORK_TYPE_EVDO_A:
          case TelephonyManager.NETWORK_TYPE_UMTS:
          case TelephonyManager.NETWORK_TYPE_EVDO_0:
          case TelephonyManager.NETWORK_TYPE_HSDPA:
          case TelephonyManager.NETWORK_TYPE_HSUPA:
          case TelephonyManager.NETWORK_TYPE_HSPA:
          case TelephonyManager.NETWORK_TYPE_EVDO_B:
          case TelephonyManager.NETWORK_TYPE_EHRPD:
          case TelephonyManager.NETWORK_TYPE_HSPAP:
            netType = NetworkType.NETWORK_3G;
            break;

          case NETWORK_TYPE_IWLAN:
          case TelephonyManager.NETWORK_TYPE_LTE:
            netType = NetworkType.NETWORK_4G;
            break;
          default:

            String subtypeName = info.getSubtypeName();
            if (subtypeName.equalsIgnoreCase("TD-SCDMA")
                || subtypeName.equalsIgnoreCase("WCDMA")
                || subtypeName.equalsIgnoreCase("CDMA2000")) {
              netType = NetworkType.NETWORK_3G;
            } else {
              netType = NetworkType.NETWORK_UNKNOWN;
            }
            break;
        }
      } else {
        netType = NetworkType.NETWORK_UNKNOWN;
      }
    }
    return netType;
  }

  @RequiresPermission(ACCESS_NETWORK_STATE)
  private static NetworkInfo getActiveNetworkInfo() {
    ConnectivityManager manager =
        (ConnectivityManager) Utils.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
    if (manager == null) {
      return null;
    }
    return manager.getActiveNetworkInfo();
  }

  /**
   * Return the ip address.
   * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
   *
   * @param useIPv4 True to use ipv4, false otherwise.
   * @return the ip address
   */
  @RequiresPermission(INTERNET)
  public static String getIPAddress(final boolean useIPv4) {
    try {
      Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
      while (nis.hasMoreElements()) {
        NetworkInterface ni = nis.nextElement();
        // To prevent phone of xiaomi return "10.0.2.15"
        if (!ni.isUp()) {
          continue;
        }
        Enumeration<InetAddress> addresses = ni.getInetAddresses();
        while (addresses.hasMoreElements()) {
          InetAddress inetAddress = addresses.nextElement();
          if (!inetAddress.isLoopbackAddress()) {
            String hostAddress = inetAddress.getHostAddress();
            boolean isIPv4 = hostAddress.indexOf(':') < 0;
            if (useIPv4) {
              if (isIPv4) {
                return hostAddress;
              }
            } else {
              if (!isIPv4) {
                int index = hostAddress.indexOf('%');
                return index < 0
                    ? hostAddress.toUpperCase()
                    : hostAddress.substring(0, index).toUpperCase();
              }
            }
          }
        }
      }
    } catch (SocketException e) {
      e.printStackTrace();
    }
    return null;
  }

  /**
   * Return the domain address.
   * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
   *
   * @param domain The name of domain.
   * @return the domain address
   */
  @RequiresPermission(INTERNET)
  public static String getDomainAddress(final String domain) {
    InetAddress inetAddress;
    try {
      inetAddress = InetAddress.getByName(domain);
      return inetAddress.getHostAddress();
    } catch (UnknownHostException e) {
      e.printStackTrace();
      return null;
    }
  }

  public enum NetworkType {
    NETWORK_WIFI,
    NETWORK_4G,
    NETWORK_3G,
    NETWORK_2G,
    NETWORK_UNKNOWN,
    NETWORK_NO
  }
}